use chrono::Utc;
use sea_orm::QueryResult;
use sea_query::{
    Alias, DynIden, Expr, Index, IndexCreateStatement, IntoColumnRef, IntoIden, SimpleExpr,
};
use serde::Deserialize;

use crate::JsonValue;

use super::default_value_fun::DefaultValueFunc;

macro_rules! ss {
    ($re:ident,$value:expr) => {
        if $value == stringify!($re) {
            return Ok(Self::$re);
        }
    };
}

#[derive(Debug, Clone, Deserialize)]
pub enum ColumnType {
    Char,
    String,
    Text,
    Boolean,
    TinyInteger,
    SmallInteger,
    Integer,
    BigInteger,
    TinyUnsigned,
    SmallUnsigned,
    Unsigned,
    BigUnsigned,
    Float,
    Double,
    DateTime,
    Timestamp,
    Time,
    Date,
    Json,
}

impl ColumnType {
    pub fn map_query_value(
        &self,
        query_result: &QueryResult,
        pre: &str,
        col: &str,
    ) -> Result<JsonValue, sea_orm::DbErr> {
        match self {
            ColumnType::TinyInteger => query_result.try_get::<u8>(pre, col).map(From::from),
            ColumnType::TinyUnsigned => query_result.try_get::<i8>(pre, col).map(From::from),
            ColumnType::SmallInteger => query_result.try_get::<i16>(pre, col).map(From::from),
            ColumnType::SmallUnsigned => query_result.try_get::<u16>(pre, col).map(From::from),
            ColumnType::Integer => query_result.try_get::<i32>(pre, col).map(From::from),
            ColumnType::Unsigned => query_result.try_get::<u32>(pre, col).map(From::from),
            ColumnType::BigInteger => query_result.try_get::<i64>(pre, col).map(From::from),
            ColumnType::BigUnsigned => query_result.try_get::<u64>(pre, col).map(From::from),
            ColumnType::Json => query_result.try_get::<JsonValue>(pre, col),
            ColumnType::Boolean => query_result.try_get::<bool>(pre, col).map(From::from),
            ColumnType::Double | ColumnType::Float => {
                query_result.try_get::<f64>(pre, col).map(From::from)
            }
            ColumnType::Char | ColumnType::Text | ColumnType::String => {
                query_result.try_get::<String>(pre, col).map(From::from)
            }
            ColumnType::DateTime => query_result
                .try_get::<chrono::NaiveDateTime>(pre, col)
                .map(|d| d.to_string())
                .map(From::from),
            ColumnType::Timestamp => query_result
                .try_get::<chrono::DateTime<Utc>>(pre, col)
                .map(|d| d.to_string())
                .map(From::from),
            ColumnType::Time => todo!(),
            ColumnType::Date => todo!(),
        }
    }

    /// 将json value 转换为 [SimpleExpr], 在更新和插入数据时使用
    /// 当jsonvalue的值类型和[ColumnType]不匹配时，返回空
    pub fn expr_of(&self, v: &JsonValue) -> Option<SimpleExpr> {
        match self {
            Self::Date | Self::Time | Self::DateTime | Self::Text | Self::String | Self::Char => {
                v.as_str().map(Into::into)
            }
            Self::Boolean => v.as_bool().map(Into::into),
            Self::TinyInteger | Self::SmallInteger | Self::Integer | Self::BigInteger => {
                v.as_i64().map(Into::into)
            }
            Self::TinyUnsigned | Self::SmallUnsigned | Self::Unsigned | Self::BigUnsigned => {
                v.as_u64().map(Into::into)
            }
            Self::Float | Self::Double => v.as_f64().map(Into::into),
            Self::Timestamp => todo!(),
            Self::Json => Some(v.to_string().into()),
        }
    }
}

impl TryFrom<String> for ColumnType {
    type Error = String;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        ss!(DateTime, value);
        ss!(String, value);
        ss!(Text, value);
        ss!(BigInteger, value);
        ss!(Unsigned, value);
        ss!(BigUnsigned, value);
        ss!(Timestamp, value);
        ss!(Integer, value);
        ss!(Boolean, value);
        ss!(DateTime, value);
        Err(format!("unknown variant `{}`", value))
    }
}

#[derive(Debug, Clone, Deserialize)]
#[allow(dead_code)]
pub struct GenColumn {
    pub(crate) name: String,
    column_name: Option<String>,
    column_type: ColumnType,
    column_type_len: Option<u32>,
    column_comment: Option<String>,
    #[serde(default)]
    pub(crate) is_pk: bool,
    #[serde(default)]
    pub(crate) is_increment: bool,
    #[serde(default)]
    pub(crate) is_required: bool,
    /// 是否展示
    #[serde(default)]
    pub(crate) is_hide: bool,
    /// 是否保护，不允许用户赋值，只能通过 DefaultValueFunc
    #[serde(default)]
    pub(crate) is_protect: bool,
    #[serde(default)]
    pub(crate) sort: u16,
    /// 插入数据的默认值
    default: Option<DefaultValueFunc>,
    /// 在更新数据是的默认值
    update: Option<DefaultValueFunc>,
}

impl Into<DynIden> for &GenColumn {
    fn into(self) -> DynIden {
        Alias::new(self.column_name()).into_iden()
    }
}

impl IntoColumnRef for &GenColumn {
    fn into_column_ref(self) -> sea_query::ColumnRef {
        Alias::new(self.column_name()).into_column_ref()
    }
}

impl Into<Expr> for &GenColumn {
    fn into(self) -> Expr {
        Expr::col(self)
    }
}

impl GenColumn {
    pub fn new<S: Into<String>>(name: S, column_type: ColumnType) -> Self {
        Self {
            name: name.into(),
            column_name: None,
            column_type,
            column_type_len: None,
            column_comment: None,
            is_pk: false,
            is_increment: false,
            is_required: false,
            is_hide: true,
            is_protect: true,
            sort: 0,
            default: None,
            update: None,
        }
    }

    /// 返回column的定义，创建table需要使用
    pub fn column_def(&self) -> sea_query::ColumnDef {
        let mut def = sea_query::ColumnDef::new(Into::<DynIden>::into(self));
        if self.is_required {
            def.not_null();
        }
        if self.is_increment {
            def.auto_increment();
        }
        if let Some(comment) = &self.column_comment {
            def.comment(comment);
        }
        match self.column_type {
            ColumnType::TinyInteger => def.tiny_integer(),
            ColumnType::TinyUnsigned => def.tiny_unsigned(),
            ColumnType::SmallInteger => def.small_integer(),
            ColumnType::SmallUnsigned => def.small_unsigned(),
            ColumnType::Integer => def.integer(),
            ColumnType::Unsigned => def.unsigned(),
            ColumnType::BigInteger => def.big_integer(),
            ColumnType::BigUnsigned => def.big_unsigned(),
            ColumnType::Boolean => def.boolean(),
            ColumnType::Text => def.text(),
            ColumnType::Json => def.json(),
            ColumnType::Float => def.float(),
            ColumnType::Double => def.double(),
            ColumnType::DateTime => def.date_time(),
            ColumnType::Timestamp => def.timestamp(),
            ColumnType::Time => def.time(),
            ColumnType::Date => def.date(),
            ColumnType::String => match self.column_type_len {
                Some(l) => def.string().string_len(l),
                None => def.string(),
            },
            ColumnType::Char => match self.column_type_len {
                Some(l) => def.char().char_len(l),
                None => def.char(),
            },
        };
        def
    }

    #[inline]
    pub fn expr(&self) -> Expr {
        Expr::col(self)
    }

    #[inline]
    pub fn iden(&self) -> DynIden {
        self.into()
    }

    /// 尝试从[QueryResult]获取[serde_json::Value]
    pub fn try_get_value(&self, query_result: &QueryResult) -> Result<JsonValue, sea_orm::DbErr> {
        // 不需要前缀，查询的col都被as成name
        self.column_type
            .map_query_value(query_result, "", &self.name)
    }

    pub fn name(&self) -> &str {
        self.column_name.as_ref().unwrap_or(&self.name)
    }

    pub fn default_value_expr(&self) -> Option<SimpleExpr> {
        self.default.as_ref().map(Into::into)
    }

    pub fn update_value_expr(&self) -> Option<SimpleExpr> {
        self.update.as_ref().map(Into::into)
    }

    #[inline]
    pub fn get_value_expr_from(&self, v: &JsonValue) -> Option<SimpleExpr> {
        self.column_type.expr_of(v)
    }

    /// 返回类型
    pub fn type_(&self) -> &ColumnType {
        &self.column_type
    }

    pub fn column_name(&self) -> &String {
        self.column_name.as_ref().unwrap_or(&self.name)
    }

    pub fn set_pk(mut self, is_pk: bool) -> Self {
        self.is_pk = is_pk;
        self
    }

    pub fn set_increment(mut self, is_increment: bool) -> Self {
        self.is_increment = is_increment;
        self
    }

    pub fn set_required(mut self, is_required: bool) -> Self {
        self.is_required = is_required;
        self
    }

    pub fn set_hide(mut self, is_show: bool) -> Self {
        self.is_hide = is_show;
        self
    }

    pub fn column_type_len(mut self, len: Option<u32>) -> Self {
        self.column_type_len = len;
        self
    }

    pub fn set_protect(mut self, is_protect: bool) -> Self {
        self.is_protect = is_protect;
        self
    }

    pub fn set_sort(mut self, sort: u16) -> Self {
        self.sort = sort;
        self
    }

    pub fn set_column_name(mut self, column_name: Option<String>) -> Self {
        self.column_name = column_name;
        self
    }

    pub fn set_column_comment(mut self, column_comment: Option<String>) -> Self {
        self.column_comment = column_comment;
        self
    }

    pub fn set_default(mut self, default: Option<DefaultValueFunc>) -> Self {
        self.default = default;
        self
    }

    pub fn set_update(mut self, update: Option<DefaultValueFunc>) -> Self {
        self.update = update;
        self
    }
}

#[derive(Debug, Clone, Deserialize)]
pub struct GenIndex {
    name: Option<String>,
    columns: Vec<String>,
    primary: Option<bool>,
    unique: Option<bool>,
}

impl GenIndex {
    pub fn index(&self) -> IndexCreateStatement {
        let mut index = Index::create();
        if let Some(name) = &self.name {
            index.name(name);
        }
        if Some(true) == self.primary {
            index.primary();
        }
        if Some(true) == self.unique {
            index.unique();
        }
        for col in &self.columns {
            index.col(Alias::new(col));
        }
        return index;
    }

    pub fn is_primary(&self) -> bool {
        self.primary == Some(true)
    }
}
